#include "network.hh"
#include "module.hh"

#include <fstream>
#include <cassert>
#include <memory>
#include <iostream>
#include <algorithm>
#include <queue>
#include <sstream>
#include <stdexcept>

using File = std::ifstream;

template <typename T>
using Queue = std::queue<T>;

ModuleInfo::ModuleInfo(const String& line) 
    : type{'\0'}, name{}, outputs{}
{
    // Module type
    this->type = line.front();

    const std::size_t minus_pos {line.find('-')};
    const std::size_t name_end_pos {minus_pos - 1};
    const std::size_t output_begin_pos {minus_pos + 3};

    // Module name
    const std::size_t name_begin_pos {this->type == 'b' ? 0 : 1};
    this->name = line.substr(name_begin_pos, name_end_pos - name_begin_pos);

    // Module Outputs
    std::size_t begin {output_begin_pos};
    std::size_t end {line.find(", ", begin)};
    while (end != String::npos)
    {
        this->outputs.emplace_back(line.substr(begin, end - begin));
        begin = end + 2;
        end = line.find(", ", begin);
    }
    this->outputs.emplace_back(line.substr(begin, end - begin));
}

Network::Network(const String& filename) :
    _flips{},
    _begins{},
    _modules{}
{
    // Open file
    File infile {filename};
    String line {};
    Vector<ModuleInfo> infos {};
    while (std::getline(infile, line))
        infos.emplace_back(line);

    this->create_modules(infos);
    this->connect_modules(infos);
}

auto Network::print() -> void
{
    std::for_each(
        this->_modules.begin(), 
        this->_modules.end(), 
        [](const std::pair<String, Rc<Module>>& kv){
            kv.second->print();
        }
    );
}

auto Network::push_button() -> std::tuple<std::size_t, std::size_t>
{
    Queue<Rc<Module>> queue {};
    std::size_t low_size = 0;
    std::size_t high_size = 0;

    // Push button, a low => broadcaster
    queue.push(this->_broadcaster);
    while (!queue.empty())
    {
        Rc<Module> module {queue.front()};
        queue.pop();

        if (module->update())
            for (Rc<Module> next : module->outputs())
                queue.push(next);
    }

    return std::make_tuple(low_size, high_size);
}

auto Network::create_modules(const Vector<ModuleInfo> infos) -> void
{
    
    for (const ModuleInfo& info : infos) 
    {
        switch (info.type)
        {
            case 'b': break;
            case '$': this->add_module(info.name, ModuleKind::Conjunction); break;
            case '%': this->add_module(info.name, ModuleKind::Flipflop); break;
        } 
    }
}

auto Network::connect_modules(const Vector<ModuleInfo> infos) -> void
{
    for (const ModuleInfo& line : infos)
    {
        switch (info.type)
        {

        }
        auto [name, kind] {this->parse_name_and_kind(line)};
        Rc<Module> module {this->get_module(name)};

        Vector<String> output_names {this->parse_output_names(line)};
        for (const String& name: output_names)
        {
            Rc<Module> output {this->get_module(name)};
            module->add_output(output);
            output->add_intput(module);
        }
    }
}

auto Network::add_module(String name, ModuleKind kind) -> void
{
    Rc<Module> module {};
    switch (kind)
    {
    case ModuleKind::Conjunction: 
        module = std::make_shared<Conjunction>(name); break;
    case ModuleKind::Flipflop: 
    {
        module = std::make_shared<Flipflop>(name);
        this->_flips.emplace_back(module);
        break;
    }
    default: std::exit(1);
    }

    this->_modules.emplace(std::move(name), module);
}

auto Network::get_module(const String& name) -> Rc<Module>
{
    auto find_res {this->_modules.find(name)};
    if (find_res == this->_modules.end())
        return std::make_shared<Output>(name);
    return find_res->second;
}

auto Network::parse_name_and_kind(const String& line) -> std::tuple<String, ModuleKind>
{
    std::size_t minus_pos {line.find('-')};
    if (minus_pos == String::npos)
        std::exit(21);

    std::size_t name_begin_pos {0};
    ModuleKind kind {ModuleKind::Broadcaster};

    // First character means module type
    const char module_type {line.front()};
    switch (module_type)
    {
    case '%':
        name_begin_pos = 1;
        kind = ModuleKind::Flipflop;
        break;
    case '&':
        name_begin_pos = 1;
        kind = ModuleKind::Conjunction;
        break;
    default: break;
    }

    return std::make_tuple(
                line.substr(name_begin_pos, minus_pos - 1 - name_begin_pos), 
                kind);
}

auto Network::parse_output_names(const String& line) -> Vector<String>
{
    // &kx -> zs, br, jd, bj, vg
    std::size_t minus_pos {line.find('-')};
    if (minus_pos == String::npos)
        std::exit(21);

    Vector<String> output_names {};
    std::size_t begin {minus_pos + 3};
    std::size_t end {begin + 1};
    std::size_t size {1};

    while (end < line.size())
    {
        if (line[end] != ',')
        {
            end += 1;
            size += 1;
            continue;
        }
        else
        {
            output_names.emplace_back(line.substr(begin, size));
            begin = end + 2;
            end = begin + 1; 
            size = 1;
        }
    }
    output_names.emplace_back(line.substr(begin, size));

    return output_names;
}